/*
 *  Copyright (C) 2010 Glauco
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.balisunrise.annotation.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.balisunrise.annotation.Action;
import org.balisunrise.annotation.VisualAction;
import org.balisunrise.annotation.ActionCancel;
import org.balisunrise.annotation.ActionConfirm;
import org.balisunrise.annotation.ActionMap;
import org.balisunrise.annotation.EventType;
import org.balisunrise.annotation.VisualActionTarget;
import org.balisunrise.application.Application;
import org.balisunrise.common.Event;
import org.balisunrise.common.IconManager;
import org.balisunrise.common.PropertiesReader;
import org.balisunrise.common.TransactionActions;

/**
 *
 * @author Glauco
 */
public class ActionUtilities {

    /**
     * Mensagem de erro para Actions.
     */
    private static final String MESSAGE = "Erro ao ler Annotation \"Action\".";

    /**
     * Kind da Action.
     * Zero (0) para Action.
     * Um (1) para Visual Action.
     */
    public static final int ACTION = 0;

    /**
     * Kind da Action.
     * Zero (0) para Action.
     * Um (1) para Visual Action.
     */
    public static final int VISUAL_ACTION = 1;

    /**
     * Tipo da Action.
     * Zero (0) para Action.
     * Um (1) para Visual Action.
     */
    public int actionKind;

    /**
     * Classe do objeto que deve executar a ação.
     */
    public Class actionMapClass;

    /**
     * Entidades onde esta ação será executada.
     */
    public Class[] entityClasses;

    /**
     * Alias (nome) da definição.
     */
    public String[] alias;

    /**
     * Tipos de Eventos que essa ação executa.
     * Utilizado para <code>@Action</code>.
     */
    public EventType[] actionTargets;

    /**
     * Tipos de Ação Visual que essa ação representa.
     * Utilizado para <code>@VisualAction</code>.
     */
    public VisualActionTarget[] visualActionTargets;

    /**
     * Propriedades para execução da ação.
     */
    public String[] properties;

    /**
     * Método a ser executado do mapa de ação.
     * Método com a anotação <code>@Action</code> ou <code>@VisualAction</code>.
     */
    public Method method;

    /**
     * Flag que informa se o método recebe um parâmetro Event
     * @see org.balisunrise.common.Event
     */
    public boolean withParm;

    /**
     * Indice em que a ação deve aparecer na tela.
     * Atributo do VisualAction.
     */
    public int index;

    /**
     * Icone da ação.
     * Atributo do VisualAction.
     */
    public String icon;

    /**
     * Texto da ação.
     * Atributo do VisualAction.
     */
    public String text;

    /**
     * Explicação para a ação.
     * Atributo do VisualAction.
     */
    public String toolTipText;

    /**
     * Atalho para a ação.
     * Atributo do VisualAction.
     */
    public String accelerator;

    /**
     * Determina se a representação visual será focusable.
     * Atributo do VisualAction.
     */
    public boolean focusable;

    /**
     * Organiza as ações de uma lista pelo indice das ações.
     * @param list
     * @throws AnnotationsUtilitiesException
     */
    public static void sort(List<ActionUtilities> list)
            throws AnnotationsUtilitiesException {

        if (list == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"list\" não pode ser nulo!");
        }

        //Ordena a lista
        Collections.sort(list, new Comparator<ActionUtilities>() {

            @Override
            public int compare(ActionUtilities o1, ActionUtilities o2) {

                int order1 = o1.index;
                int order2 = o2.index;

                // compara
                return order1 < order2 ? -1 : (order1 > order2 ? 1 : 0);

            }
        });
    }

    /**
     * Verifica os parametros do método.
     * Se não tiver parametros retorna false,
     * se tiver mais de um parametro dispara uma Exception,
     * se tiver 1 parametro e for um Event retorna verdadeiro,
     * caso contrário dispara uma Exception.
     */
    private static boolean withParm(Method method) throws Exception{

        Class[] params = method.getParameterTypes();

        if (params.length == 0)
            return false;

        if (params.length > 1)
            throw new Exception();

        if (params[0].isAssignableFrom(Event.class))
            return true;

        if(Event.class.isAssignableFrom(params[0]))
            return true;

        throw new Exception();
    }

    /**
     * Cria uma lista de utilitarios para ações para um mapa de ação,
     * o qual deve possuir uma Annotation ActionMap.
     * @see org.balisunrise.annotation.ActionMap
     */
    public static List<ActionUtilities> createVisualActions(Class actionMap)
            throws AnnotationsUtilitiesException {

        if (actionMap == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"actionMap\" não pode ser nulo!");
        }

        if(!actionMap.isAnnotationPresent(ActionMap.class))
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nO Mapa de Ações não possuí a Annotation \"ActionMap\"!");


        ActionMap am = (ActionMap) actionMap.getAnnotation(ActionMap.class);

        return createVisualActions(actionMap, am.classes());
    }

    /**
     * Cria uma lista de utilitários para ações onde a entidade é definida
     * por parâmetro.
     */
    public static List<ActionUtilities> createVisualActions(Class actionMap,
            Class[] entityClasses)
            throws AnnotationsUtilitiesException {

        if (actionMap == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"actionMap\" não pode ser nulo!");
        }

        if (entityClasses == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"entityClasses\" não pode ser nulo!");
        }

        List<ActionUtilities> actions = new ArrayList<ActionUtilities>();

        for (Method method : actionMap.getMethods()) {
            if (method.isAnnotationPresent(VisualAction.class)) {
                VisualAction a = method.getAnnotation(VisualAction.class);
                ActionUtilities au = new ActionUtilities();
                try{
                    au.withParm = withParm(method);
                }catch(Exception ex){
                    continue;
                }
                au.actionKind = VISUAL_ACTION;
                au.entityClasses = entityClasses;
                au.alias = a.alias();
                au.visualActionTargets = a.target();
                au.properties = a.properties();
                au.actionMapClass = actionMap;
                au.method = method;
                au.index = a.index();
                au.icon = a.icon();
                au.text = a.text();
                au.toolTipText = a.toolTipText();
                au.focusable = a.focusable();
                actions.add(au);

            }
        }

        return actions;
    }

    /**
     * Cria uma lista de utilitarios para ações para um mapa de ação,
     * o qual deve possuir uma Annotation ActionMap.
     * @see org.balisunrise.annotation.ActionMap
     */
    public static List<ActionUtilities> createAction(Class actionMap)
            throws AnnotationsUtilitiesException {

        if (actionMap == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"actionMap\" não pode ser nulo!");
        }

        if(!actionMap.isAnnotationPresent(ActionMap.class))
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nO Mapa de Ações não possuí a Annotation \"ActionMap\"!");


        ActionMap am = (ActionMap) actionMap.getAnnotation(ActionMap.class);

        return createAction(actionMap, am.classes());

    }

    /**
     * Cria uma lista de utilitários para ações onde a entidade é definida
     * por parâmetro.
     */
    public static List<ActionUtilities> createAction(Class actionMap,
            Class[] entityClasses)
            throws AnnotationsUtilitiesException {

        if (actionMap == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"actionMap\" não pode ser nulo!");
        }

        if (entityClasses == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"entityClasses\" não pode ser nulo!");
        }

        List<ActionUtilities> actions = new ArrayList<ActionUtilities>();

        for (Method method : actionMap.getMethods()) {
            if (method.isAnnotationPresent(Action.class)) {
                Action a = method.getAnnotation(Action.class);
                ActionUtilities au = new ActionUtilities();
                try{
                    au.withParm = withParm(method);
                }catch(Exception ex){
                    continue;
                }
                au.actionKind = ACTION;
                au.entityClasses = entityClasses;
                au.alias = a.alias();
                au.actionTargets = a.target();
                au.properties = a.properties();
                au.actionMapClass = actionMap;
                au.method = method;
                actions.add(au);
            }
        }

        return actions;

    }

    public static ActionUtilities createConfirmationAction(Class actionMap,
            String alias) throws AnnotationsUtilitiesException {

        return createAction(actionMap, alias, ActionConfirm.class);
    }

    public static ActionUtilities createCancellationAction(Class actionMap,
            String alias) throws AnnotationsUtilitiesException {

        return createAction(actionMap, alias, ActionCancel.class);
    }

    private static ActionUtilities createAction(Class actionMap,
            String alias, Class annotationClass)
            throws AnnotationsUtilitiesException {

        if (actionMap == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"actionMap\" não pode ser nulo!");
        }

        if (alias == null) {
            throw new AnnotationsUtilitiesException(MESSAGE +
                    "\nParâmentro \"alias\" não pode ser nulo!");
        }

        for (Method method : actionMap.getMethods()) {
            if (annotationClass.equals(ActionConfirm.class)) {
                if (method.isAnnotationPresent(ActionConfirm.class)) {
                    ActionConfirm a = method.getAnnotation(ActionConfirm.class);
                    ActionUtilities au = new ActionUtilities();
                    try{
                        au.withParm = withParm(method);
                    }catch(Exception ex){
                        continue;
                    }
                    au.actionKind = VISUAL_ACTION;
                    au.entityClasses = new Class[0];
                    au.alias = a.alias();
                    au.visualActionTargets = new VisualActionTarget[0];
                    au.actionMapClass = actionMap;
                    au.method = method;
                    au.index = 0;
                    au.icon = IconManager.CONFIRM;
                    au.text = a.text();
                    au.toolTipText = a.toolTipText();
                    au.accelerator = "F10";
                    au.focusable = true;
                    return au;
                }
            } else if (annotationClass.equals(ActionCancel.class)) {
                if (method.isAnnotationPresent(ActionCancel.class)) {
                    ActionCancel a = method.getAnnotation(ActionCancel.class);
                    ActionUtilities au = new ActionUtilities();
                    try{
                        au.withParm = withParm(method);
                    }catch(Exception ex){
                        continue;
                    }
                    au.actionKind = VISUAL_ACTION;
                    au.entityClasses = new Class[0];
                    au.alias = a.alias();
                    au.visualActionTargets = new VisualActionTarget[0];
                    au.actionMapClass = actionMap;
                    au.method = method;
                    au.index = 0;
                    au.icon = IconManager.CANCEL;
                    au.text = a.text();
                    au.toolTipText = a.toolTipText();
                    au.focusable = true;
                    return au;
                }
            }
        }

        String className = annotationClass.getName();

        throw new AnnotationsUtilitiesException(MESSAGE +
                "\nAnotação \"" + className +
                "\" não encontrada para o nome \"" + alias + "\"!");
    }

    /**
     * Cria uma lista com as quatro ações visuais da transaction action.
     * 
     * @return
     * @throws AnnotationsUtilitiesException
     */
    public static List<ActionUtilities> createTransactionActions()
            throws AnnotationsUtilitiesException{

        PropertiesReader pr = Application.get().getPropertiesReader();

        //
        List<ActionUtilities> auList = new ArrayList<ActionUtilities>(4);

        String sIcon = IconManager.SEARCH;
        String nIcon = IconManager.ADD;
        String eIcon = IconManager.EDIT;
        String dIcon = IconManager.REMOVE;

        // cria ações
        for (Method method : TransactionActions.class.getMethods()) {

            if (method.getName().equals("performSearch")) {

                ActionUtilities au = new ActionUtilities();
                try{
                    au.withParm = withParm(method);
                }catch(Exception ex){
                    continue;
                }
                au.actionKind = VISUAL_ACTION;
                au.entityClasses = new Class[0];
                au.alias = new String[0];
                au.visualActionTargets = new VisualActionTarget[0];
                au.actionMapClass = TransactionActions.class;
                au.method = method;
                au.index = 0;
                au.icon = sIcon;
                au.text = null;
                au.toolTipText = pr.readProperty(pr.searchToolTipText);
                au.accelerator = "F8";
                au.focusable = false;
                auList.add(au);
            }
            if (method.getName().equals("performNew")) {
                ActionUtilities au = new ActionUtilities();
                try{
                    au.withParm = withParm(method);
                }catch(Exception ex){
                    continue;
                }
                au.actionKind = VISUAL_ACTION;
                au.entityClasses = new Class[0];
                au.alias = new String[0];
                au.visualActionTargets = new VisualActionTarget[0];
                au.actionMapClass = TransactionActions.class;
                au.method = method;
                au.index = 1;
                au.icon = nIcon;
                au.text = null;
                au.toolTipText = pr.readProperty(pr.newToolTipText);
                au.accelerator = "F3";
                au.focusable = false;
                auList.add(au);
            }
            if (method.getName().equals("performEdit")) {
                ActionUtilities au = new ActionUtilities();
                try{
                    au.withParm = withParm(method);
                }catch(Exception ex){
                    continue;
                }
                au.actionKind = VISUAL_ACTION;
                au.entityClasses = new Class[0];
                au.alias = new String[0];
                au.visualActionTargets = new VisualActionTarget[0];
                au.actionMapClass = TransactionActions.class;
                au.method = method;
                au.index = 2;
                au.icon = eIcon;
                au.text = null;
                au.toolTipText = pr.readProperty(pr.editToolTipText);
                au.accelerator = "F7";
                au.focusable = false;
                auList.add(au);
            }
            if (method.getName().equals("performDelete")) {
                ActionUtilities au = new ActionUtilities();
                try{
                    au.withParm = withParm(method);
                }catch(Exception ex){
                    continue;
                }
                au.actionKind = VISUAL_ACTION;
                au.entityClasses = new Class[0];
                au.alias = new String[0];
                au.visualActionTargets = new VisualActionTarget[0];
                au.actionMapClass = TransactionActions.class;
                au.method = method;
                au.index = 3;
                au.icon = dIcon;
                au.text = null;
                au.toolTipText = pr.readProperty(pr.deleteToolTipText);
                au.accelerator = "F6";
                au.focusable = false;
                auList.add(au);
            }
        }

        ActionUtilities.sort(auList);

        return auList;
    }

    /**
     * Verifica se a ação é uma @Action e está dentro dos requisitos dos
     * EventType, alias e propriedades recebidos.
     *
     * @param target Tipo de Evento em que a ação deve ser executada.
     * @param entityClass Classe da Entidade.
     * @param alias Alias das VisualEntities em que o evento deve ser escutado.
     * @param props Propriedades que a ação deve possuir.
     * @return Verdadeiro se o evento contemplar as condições. Falso caso
     * contrário.
     */
    public boolean verify(EventType target, Class entityClass,
            String alias, String props){

        if(actionKind != ACTION)
            return false;

        boolean founded = false;

        for (EventType et : actionTargets) {
            if(et.equals(target)){
                founded = true;
                break;
            }
        }

        if(!founded)
            return founded;

        return verify(entityClass, alias, props);
    }

    /**
     * Verifica se a ação é uma @VisualAction e está dentro dos requisitos dos
     * EventType, alias e propriedades recebidos.
     *
     * @param target Entidade visual em que a ação visual deve aparecer.
     * @param entityClass Classe da Entidade.
     * @param alias Alias das VisualEntities em que o evento deve ser escutado.
     * @param props Propriedades que a ação deve possuir.
     * @return Verdadeiro se o evento contemplar as condições. Falso caso
     * contrário.
     */
    public boolean verify(VisualActionTarget target, Class entityClass,
            String alias, String props){

        if(actionKind != VISUAL_ACTION)
            return false;

        boolean founded = false;

        for (VisualActionTarget vat : visualActionTargets) {
            if(vat.equals(target)){
                founded = true;
                break;
            }
        }

        if(!founded)
            return founded;

        return verify(entityClass, alias, props);
    }

    private boolean verify(Class entityClass, String alias, String properties){

        boolean founded = false;

        if (this.entityClasses.length == 0)
            founded = true;
        else
            for (Class ec : entityClasses) {
                if(ec.isAssignableFrom(entityClass)){
                    founded = true;
                    break ;
                }
            }

        if(!founded)
            return founded;

        founded = false;

        if(this.alias.length == 0)
            founded = true;
        else
            for (String a : this.alias) {
                if(a.equals(alias)){
                    founded = true;
                    break;
                }
            }

        if(!founded)
            return founded;

        founded = false;

        if(this.properties.length == 0)
            founded = true;
        else
            for (String p : this.properties) {
                if(p.equals(properties)){
                    founded = true;
                    break;
                }
            }

        return founded;
    }
}
